/**
 * <copyright>
 * Copyrigth 2007 Aurelien Moreau
 *
 * This file is part of ProcessShop.
 *
 * ProcessShop is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * ProcessShop is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with ProcessShop; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * </copyright>
 *
 * $Id$
 */
package net.yienyien.process.folder.componentizer.impl;

import java.util.Collection;

import net.yienyien.process.bpd.BPDComponent;
import net.yienyien.process.bpd.BPDObject;
import net.yienyien.process.bpd.ControlFlowRelation;
import net.yienyien.process.folder.artifacts.ArtifactsFactory;
import net.yienyien.process.folder.artifacts.BPDPattern;
import net.yienyien.process.folder.artifacts.PatternedBPDComponent;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;

public abstract class BPDPatternImpl implements BPDPattern {

	private String name;
	
	
	private EClass sourceType;
	private EClass sinkType;
	private EClass[] contentTypes;
	
	public BPDPatternImpl(String name, EClass sourceType, EClass sinkType, EClass... contentTypes) {
		this.name=name;
		this.sourceType=sourceType;
		this.sinkType=sinkType;
		this.contentTypes=contentTypes;
	}

	@Override
	public String getName() {
		return name;
	}
	
	@Override
	public String toString() {
		return name;
	}
	
	static protected final boolean isConnected(BPDObject s, BPDObject o) {
		for(ControlFlowRelation r: s.getOutputs()) {
			if(o.equals(r.getOutput())) {
				return true;
			}
		}
		return false;
	}
	
	protected boolean accept(BPDObject source, BPDObject sink) {
		return sourceType.isInstance(source) &&
			sinkType.isInstance(sink) && !source.equals(sink);
	}
	
	protected final boolean isValidContent(BPDObject o) {
		for (EClass type : contentTypes) {
			if (type.isInstance(o)) {
				return true;
			}
		}
		return false;
	}
	
	protected final boolean isValidElement(BPDObject source, BPDObject sink, Collection<BPDObject> objects, BPDObject o) {
		for(ControlFlowRelation r: o.getInputs()) {
			BPDObject in=r.getInput();
			if(!in.equals(source) && !in.equals(sink) && !objects.contains(in)) {
				//System.out.println("Invalid input "+in.getFrom());
				return false;
			}
		}

		for(ControlFlowRelation r: o.getOutputs()) {
			BPDObject out=r.getOutput();
			if(!out.equals(source) && !out.equals(sink) && !objects.contains(out)) {
				//System.out.println("Invalid output "+out.getFrom());
				return false;
			}
		}
		
		return true;
	}
	
	protected final boolean isValidSourceSink(BPDObject source, BPDObject sink, Collection<BPDObject> objects) {
		// Entry/Exit condition
		BPDObject entry=null;
		BPDObject exit=null;
		//System.out.println("ici");
		for(ControlFlowRelation r: source.getInputs()) {
			BPDObject e=r.getInput();
			
			if(!objects.contains(e) && !source.equals(e) && !sink.equals(e)) {
				if(entry==null) {
					entry=e;
				}
				
				if(!entry.equals(e)) {
					//System.out.println("invalid source entry="+entry.getFrom()+ " current="+e.getFrom());
					return false;
				}
			}
		}
		
		for(ControlFlowRelation r: source.getOutputs()) {
			if(!r.getOutput().equals(sink) && !objects.contains(r.getOutput())) {
				//System.out.println("source out connect "+r.getInput().getFrom()+" =>"+ r.getOutput().getFrom());
				return false;
			}
		}
		
		for(ControlFlowRelation r: sink.getOutputs()) {
			BPDObject e=r.getOutput();
			
			if(!objects.contains(e) && !source.equals(e) && !sink.equals(e)) {
				if(exit==null) {
					exit=e;
				}
				
				if(!exit.equals(e)) {
					//System.out.println("invalid sink "+exit.getFrom()+ " current="+e.getFrom());
					return false;
				}
			}
		}
		
		for(ControlFlowRelation r: sink.getInputs()) {
			if(!r.getInput().equals(source) && !objects.contains(r.getInput())) {
				//System.out.print("sink out connect "+r.getInput().getFrom()+" =>"+ r.getOutput().getFrom());
				return false;
			}
		}
		
		if(entry==null || exit==null) {
			//System.out.println("entry/exit not found");
			return false;
		}
		
		return true;
	}
	
	protected final boolean isValidComponent(BPDObject source, BPDObject sink, Collection<BPDObject> objects) {
		if(!isValidSourceSink(source, sink, objects)) {
			//System.out.println("invalidSourceSink");
			return false;
		}

		for(BPDObject o: objects) {
			if (!isValidElement(source, sink, objects, o) || !isValidContent(o)) {		// Valid content types
				//System.out.println("invalid object "+o.getFrom());
				return false;
			}
		}
		
		return true;
	}
	
	private final static PatternedBPDComponent createComponent(BPDObject source, BPDObject sink, Collection<BPDObject> objects) {
		assert(source!=null);
		assert(sink!=null);
		assert(objects!=null);
		assert(!objects.contains(source));
		assert(!objects.contains(sink));
		
		PatternedBPDComponent component=ArtifactsFactory.eINSTANCE.createPatternedBPDComponent();
		
		// find entry
		for(ControlFlowRelation r:source.getInputs()) {
			if(!objects.contains(r.getInput()) && !r.getInput().equals(source) && ! r.getInput().equals(sink)) {
				component.setEntry(r.getInput());
				break;
			}
		}

		// find exit
		for(ControlFlowRelation r: sink.getOutputs()) {
			if(!objects.contains(r.getOutput()) && !r.getOutput().equals(source) && ! r.getOutput().equals(sink)) {
				component.setExit(r.getOutput());
				break;
			}
		}
		
		component.setModel(null);
		component.setSource(source);
		component.setSink(sink);
		
		for(BPDObject o: objects) {
			component.getObjects().add(o);
			component.getRelations().addAll(o.getOutputs());
			component.getRelations().addAll(o.getInputs());
		}
		component.getObjects().add(source);
		component.getRelations().addAll(source.getOutputs());
		component.getRelations().addAll(source.getInputs());
		component.getObjects().add(sink);
		component.getRelations().addAll(sink.getOutputs());
		component.getRelations().addAll(sink.getInputs());
		
		assert(component.getEntry()!=null);
		assert(component.getExit()!=null);
		
		return component;
	}
	
	@Override
	public PatternedBPDComponent find(EList<BPDObject> active) {
		//System.out.println("Try "+getName());
		for(BPDObject src: active) {
			for(BPDObject snk: active) {
				//System.out.println(src + " <=> "+snk);
				if(accept(src, snk)) {
					Collection<BPDObject> objects=getContent(src, snk);
					if(objects!=null && isValidComponent(src, snk, objects)) {
						PatternedBPDComponent component=createComponent(src, snk, objects);
						assert(verify(component)):"Not a "+name+" component";
						return component;
					}
				}
			}
		}
		return null;
	}


	
	/*
	 * Get a VALID Component content (without source and sink).
	 */
	protected abstract Collection<BPDObject> getContent(BPDObject source, BPDObject sink);
	protected abstract boolean verify(BPDComponent component);
		
}